import 'dart:async';
import 'dart:convert';
import 'dart:io';


import 'package:event_bus/event_bus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import 'CYLogger.dart';
import 'CYIsolate.dart';

class CYRequestLog {
  
  final RequestOptions request;
  final ResponseResult response;

  CYRequestLog({ this.request, this.response });
}

class CYRequest {
  static String server = "";
  static List<String> tokenApis = [];
  static VoidCallback onLogout = (){};

  static String httpProxy = "";
  static String httpsProxy = "";
  
  static CYLogger logger = CYLogger(tag: "utils.CYRequest");
  static EventBus eventBus = EventBus();
  static const LOGOUT_CODES = [403];
  static const CODES = [
    604, // 第三方登录未绑定手机号
    620, // 用户已离线
    621, // 用户正在忙碌中
    622, // 已被加入黑名单
    803, // 余额不足
    403, // 登录过期
    601, // 用户已存在
    1005, // 超过免费发送条数
  ];

  static Function logout = RequestAction.throttle(onLogout, 1000);

  Map<String, String> environment = {
    "http_proxy": httpProxy,
    "https_proxy": httpsProxy 
  };

  Future request(RequestOptions options) async {
    HttpClient client = new HttpClient();
    client.findProxy = (Uri uri) {
      return HttpClient.findProxyFromEnvironment(uri, environment: environment);
    };
    
    DateTime startDT = DateTime.now().toUtc();
    HttpClientResponse response = await client.openUrl(options.method, options.url)
        .then((HttpClientRequest request) async {
          request = await options.cloneTo(request);
          return request.close();
        });
    DateTime startDT2 = DateTime.now().toUtc();
    ResponseResult result = await options.copyWith(response, options) as ResponseResult;
    if(LOGOUT_CODES.contains(result.code)) {
      logout();
      /// 清空消息
      result.message = "";
    }
    DateTime endDT = DateTime.now().toUtc();
    result.startTime = startDT;
    result.endTime = endDT;
    result.time = endDT.millisecondsSinceEpoch - startDT.millisecondsSinceEpoch;
    result.decodeTime = endDT.millisecondsSinceEpoch - startDT2.millisecondsSinceEpoch;
    logger.info("请求路径：${options.url}");
    logger.info("请求状态：${result.code}\t${result.message}");
    logger.info("请求时间 =======> ${result.time}ms");
    logger.info("解码时间 =======> ${result.decodeTime}ms");
    logger.info("请求数据 =======>");
    logger.info(result.data);

    eventBus.fire(CYRequestLog(
      request: options,
      response: result
    ));
    return result;
  }
}

class RequestOptionsContentType {
  static final ContentType form = ContentType("application", "x-www-form-urlencoded", charset: "utf-8");
  static final ContentType formData = ContentType("multipart", "form-data", charset: "utf-8");
  static final ContentType json = ContentType.json;
}

class RequestOptionsMethod {
  static const String GET = "get";
  static const String POST = "post";
  static const String DELETE = 'delete';
  static const String PUT = "put";
  static const String HEAD = "head";
  static const String OPTIONS = "options";
  static const String PATCH = "patch";
}

class RequestOptions {

  static CYLogger logger = CYLogger(tag: "utils.CusttomRequest.RequestOptions");
  static Map<String,dynamic> defaultData = {};

  RequestOptions(this.path, { 
    this.data, 
    this.contentType,
    this.headers,
    this.method = RequestOptionsMethod.GET,
    String server
  }) {
    validServer = server ?? CYRequest.server;
    contentType ??= RequestOptionsContentType.form;

    Map<String,dynamic> _data = <String,dynamic>{};
    if(data!=null) {
      _data.addAll(data);
    }

    if(ResponseResult.token != null) { 
      _data.addAll(<String,dynamic>{
        "token": ResponseResult.token
      });
    }

    _data.addAll(defaultData);
    logger.info("路径：$path");
    logger.info("参数：$_data");

    if(contentType == RequestOptionsContentType.form) {
      
      URLQueryParams urlQueryParams = URLQueryParams(_data);
      if(method == RequestOptionsMethod.GET || method == RequestOptionsMethod.DELETE) {
        query = urlQueryParams.query;
      } else {
        formData = urlQueryParams.formData;
      }
      /// 计算过后的参数集合
      body = _data;
    }

    if(contentType == RequestOptionsContentType.json) {
       URLQueryParams urlQueryParams = URLQueryParams(_data);
       if(method == RequestOptionsMethod.GET || method == RequestOptionsMethod.DELETE) {
        query = urlQueryParams.query;
      }
    }
  }

  /// api 地址
  String path;
  /// 上传数据
  Map<String,dynamic> data;
  /// 上传文件
  File file;
  /// 默认值为 RequestOptionsContentType.form
  ContentType contentType;
  /// api 服务器
  String validServer;
  /// api 方法
  String method;
  /// 传递uri
  Uri uri;
  /// 查询数据
  String query;
  /// form表单数据
  String formData;
  /// 过滤过后的数据
  Map<String,dynamic> body;

  Uri get url {
    if(uri!=null) return uri;
    Uri _uri = Uri.parse(validServer + path);
    return Uri(
      scheme: _uri.scheme,
      host: _uri.host,
      port: _uri.port,
      path: _uri.path,
      query: query
    );
  }
  /// api 头部
  Map<String, dynamic> headers;
  Future<HttpClientRequest> cloneTo(HttpClientRequest request) async {
    request.headers.contentType = contentType;
    if(contentType == RequestOptionsContentType.form) {
      if(
        method != RequestOptionsMethod.GET 
        && method != RequestOptionsMethod.DELETE
      ) {
        request.write(formData);
      }
    }
    if(contentType == RequestOptionsContentType.json) {
      if(
        method != RequestOptionsMethod.GET 
        && method != RequestOptionsMethod.DELETE
      ) {
        request.write(jsonEncode(data));
      }
    }
    return request;
  }

  Future copyWith(HttpClientResponse response, RequestOptions options) {
    ResponseResult result = ResponseResult();
    return result.copyWith(response, path: options.path);
  }
}

class ResponseResult {
  ResponseResult({ this.contentType });
  final ContentType contentType;
  Map<String, dynamic> body;
  int code;
  String message;
  dynamic data;
  bool isSuccess;
  int time;
  int decodeTime;
  /// 开始时间
  DateTime startTime;
  /// 结束时间
  DateTime endTime;
  static String token;

  Future copyWith(HttpClientResponse response, {ContentType contentType, String path }) async {
    if(response.statusCode != 200) {
      code = response.statusCode;
      message = "$code:网络出错，请稍后重试";
      isSuccess = false;
      return this;
    }
    contentType ??= RequestOptionsContentType.json;
    Future<String> future = parseText(response);
    if(contentType == RequestOptionsContentType.json) {
      return future.then<dynamic>((String value) async {
        return await parseJSON(value, path);
      });
    }
    return future;
  }

  Future<String> parseText(HttpClientResponse response) {
    var completer = Completer<String>.sync();
    String contents = "";
    response.transform(utf8.decoder).listen((String content) {
      contents += content;
    }).onDone((){
      completer.complete(contents);
    });
    return completer.future;
  }

  fromJson(Map json) {
    body = json;
    if(body != null) {
      code = body["code"];
      message = body["message"];
      data = body["data"];
      isSuccess = code == 200;
    }
  }

  Future<ResponseResult> parseJSON(String text, String path) async {
    fromJson(jsonDecode(text));
    // fromJson(await cyJsonDecode(text));
    if(CYRequest.tokenApis.contains(path)) {
      if(data is String) {
        ResponseResult.token = data;
      }
      if(data is Map) {
        ResponseResult.token = data["token"];
      }
    }
    return this;
  }
}


class URLQueryParams {
  URLQueryParams(Map<String, dynamic> data) {
    if(data != null) {
      data.entries.forEach((item) {
        append(item.key, item.value);
      });
    }
  }

  Map<String, String> _values = <String, String>{};

  String get formData => toString();

  String get query => toString();

  // Appends a parameter to the query with receiverd key.
  void append(String key, dynamic value) {
    if (value != null) {
      _values[key] = Uri.encodeQueryComponent(value.toString());
    }
  }

  // Removes a parameter from query by key.
  void remove(String key) {
    _values?.remove(key);
  }

  // Convert to query string like the next example:
  // * param1=value1&param2=value2
  @override
  String toString() {
    String response = '';
    _values.forEach((key, value) {
      response += "$key=$value&";
    });
    return response.substring(
        0, response.length == 0 ? 0 : response.length - 1);
  }
}

typedef Future FutureCallbackFunction();

class RequestAction {
  static Function debounce(Function fn, [int t = 300]) {
    Timer _debounce;
    return () {
      if (_debounce?.isActive ?? false) {
        _debounce.cancel();
      }
      _debounce = Timer(Duration(milliseconds: t), () {
        fn();
      });
    };
  }

  static Function throttle(Function fn, [int t = 300]) {
    Timer _throttle;
    return () {
      if (_throttle?.isActive ?? false) _throttle.cancel();
      else fn();
      _throttle = Timer(Duration(milliseconds: t), () {});
    };
  }
}
